package com.googlecode.felicalib.sample;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.googlecode.felicalib.sample.exception.FelicaException;
import com.googlecode.felicalib.sample.exception.FelicaPollingException;
import com.sun.jna.Pointer;

/**
 * 'felicalib.dll'へアクセスするための定義.
 * このソースは[システム開発サンプル]から流用したものです。
 * http://yuuhayashi.blogspot.com/search/label/FeliCa
 * 
 * @author o.tanaka
 */
public class Felica {

    /**
     * ロギングインスタンス.
     */
    private static final Log LOG = LogFactory.getLog(Felica.class);

    /**
     * String _02X_02X_02X_02X_02X_02X_02X_02X_02X_02X_02X_02X_02X_02X_02X_02X
     */
    private static final String BYTE16_FORMAT =
        "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X";

    /**
     * String BYTE8_FORMAT
     */
    private static final String BYTE8_FORMAT =
        "%02X%02X%02X%02X%02X%02X%02X%02X";

    /**
     * Felica WILDCARD {@value}.
     */
    public static final int WILDCARD = 0xFFFF; // ワイルドカード

    /**
     * Felica SUICA {@value}.
     */
    public static final int SUICA = 0x0003;

    /**
     * 'PaSoRi'にかざされているFeliCaカードのIDｍ部分を１０回読み出す。 500ms間隔で１０回読む.
     * 
     * @param args
     *            未使用
     * @throws FelicaException
     *             フェリカ実行エラー
     */
    public static void main(final String[] args) throws FelicaException {
        final Felica felica = new Felica();
        for (int i = 0; i < 10; i++) {
            try {
                System.out.println(i + ":"
                        + felica.getID(Felica.WILDCARD));
                System.out.println(
                        String.valueOf(0x7309)
                    + String.valueOf((byte) i) + ":"
                    + felica.read(Felica.WILDCARD, 0x7309, (byte) i));
                System.out.println(
                        String.valueOf(0x7409)
                    + String.valueOf((byte) i) + ":"
                    + felica.read(Felica.WILDCARD, 0x7409, (byte) i));
                System.out.println(
                        String.valueOf(0x7509)
                    + String.valueOf((byte) i) + ":"
                    + felica.read(Felica.WILDCARD, 0x7509, (byte) i));
            } catch (final FelicaPollingException e) {
                Felica.LOG.debug("main method:", e);
            }
            try {
                Thread.sleep(500);
            } catch (final InterruptedException e) {
                e.printStackTrace();
            }
        }
        felica.close();
    }

    private final Pointer pasoriHandle;

    private Pointer felicaHandle;

    private static final File TMP_DIR =
        new File(System.getProperty("java.io.tmpdir"));
    private static File outFile =
        new File(Felica.TMP_DIR, "felicalib.dll");

    static {
        InputStream inputStream =
            Thread.currentThread().getContextClassLoader()
                    .getResourceAsStream("felicalib.dll");
        if (inputStream != null) {
            if (!Felica.outFile.exists()) {
                Felica.systemFileset(inputStream);
            }
        }
        inputStream = null;
        if (Felica.outFile != null) {
            System.load(Felica.outFile.getAbsolutePath());
        }
    }

    /**
     * @param inputstream felicalib.dll
     */
    private static void systemFileset(
            final InputStream inputStream) {
        OutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(
                    Felica.outFile);
            final byte[] buf = new byte[4096];
            for (int rdsz = inputStream.read(buf);
                rdsz > 0;
                rdsz = inputStream.read(buf)) {
                outputStream.write(buf, 0, rdsz);
            }
        } catch (final FileNotFoundException e) {
            e.printStackTrace();
            Felica.outFile = null;
        } catch (final IOException e) {
            e.printStackTrace();
            Felica.outFile = null;
        } finally {
            Felica.systemFilesetFinally(inputStream, outputStream);
            outputStream = null;
        }
    }

    /**
     * @param inputStream
     * @param outputStream
     */
    private static void systemFilesetFinally(
            final InputStream inputStream,
            final OutputStream outputStream) {
        try {
            inputStream.close();
            if (outputStream != null) {
                outputStream.close();
            }
        } catch (final IOException e) {
            e.printStackTrace();
            Felica.outFile = null;
        }
    }

    /**
     * コンストラクタ。ここでFelicaカードリーダへのハンドルを取得している.
     * 
     * @throws FelicaException
     *             初期ロードエラー
     */
    public Felica() throws FelicaException {
        this.pasoriHandle = FelicaLib.INSTANCE.pasori_open(null);
        if (this.pasoriHandle == null) {
            throw new FelicaException("felicalib.dll is not opend.");
        }
        if (FelicaLib.INSTANCE.pasori_init(this.pasoriHandle) != 0) {
            throw new FelicaException("PaSoRi is not connection.");
        }
    }

    /**
     * PaSoRi ハンドルをクローズする.
     * Felicaカードリーダに関する処理を終了する際に呼ぶメソッド
     * 【　void * pasori_close(pasori *p)　】
     */
    public void close() {
        if (this.felicaHandle != Pointer.NULL) {
            FelicaLib.INSTANCE.felica_free(this.felicaHandle);
        }
        if (this.pasoriHandle != Pointer.NULL) {
            FelicaLib.INSTANCE.pasori_close(this.pasoriHandle);
        }
    }

    /**
     * FelicaカードのID番号を取得するメソッド.
     * 
     * @param systemCode
     *            システムコード(例えばSuicaは0x0003、ワイルドカードは0xFFFF)
     * @return カードのID番号
     * @throws FelicaPollingException
     *             ポーリングエラー
     */
    @SuppressWarnings("boxing")
    public String getID(final int systemCode) throws FelicaPollingException {
        FelicaLib.INSTANCE.felica_free(this.felicaHandle);
        this.felicaHandle =
            FelicaLib.INSTANCE.felica_polling(this.pasoriHandle, systemCode,
                    (byte) 0, (byte) 0);
        if (this.felicaHandle == Pointer.NULL) {
            throw new FelicaPollingException("no polling executed.");
        }
        final byte[] buf = new byte[8];
        FelicaLib.INSTANCE.felica_getidm(this.felicaHandle, buf);
        return String.format(Felica.BYTE8_FORMAT,
                buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]);
    }

    /**
     * IDm 取得 .
     * 【　void felica_getidm(felica *f, uint8 *buf)　】
     * 
     * @return カードのID番号
     * @throws FelicaPollingException
     *             ポーリングエラー
     */
    public byte[] getIDm() throws FelicaPollingException {
        if (this.felicaHandle == Pointer.NULL) {
            throw new FelicaPollingException("no polling executed.");
        }
        final byte[] buf = new byte[8];
        FelicaLib.INSTANCE.felica_getidm(this.felicaHandle, buf);
        return buf;
    }

    /**
     * FelicaカードのPMmを取得するメソッド.
     * 
     * @param systemCode
     *            システムコード(例えばSuicaは0x0003、ワイルドカードは0xFFFF)
     * @return カードのPM番号
     * @throws FelicaPollingException
     *             ポーリングエラー
     */
    @SuppressWarnings("boxing")
    public String getPM(final int systemCode) throws FelicaPollingException {
        FelicaLib.INSTANCE.felica_free(this.felicaHandle);
        this.felicaHandle =
            FelicaLib.INSTANCE.felica_polling(this.pasoriHandle, systemCode,
                    (byte) 0, (byte) 0);
        if (this.felicaHandle == Pointer.NULL) {
            throw new FelicaPollingException("no polling executed.");
        }
        final byte[] buf = new byte[8];
        FelicaLib.INSTANCE.felica_getpmm(this.felicaHandle, buf);
        return String.format(Felica.BYTE8_FORMAT,
                buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]);
    }

    /**
     * PMm 取得.
     * 【　void felica_getpmm(felica *f, uint8 *buf)　】
     * 
     * @return カードのPM番号
     * @throws FelicaPollingException
     *             ポーリングエラー
     */
    public byte[] getPMm() throws FelicaPollingException {
        if (this.felicaHandle == Pointer.NULL) {
            throw new FelicaPollingException("no polling executed.");
        }
        final byte[] buf = new byte[8];
        FelicaLib.INSTANCE.felica_getpmm(this.felicaHandle, buf);
        return buf;
    }

    /**
     * FeliCa をポーリングする.
     * 【　felica*　felica_polling(pasori *p, uint16 systemcode,
     * uint8 RFU, uint8 timeslot)　】
     * 
     * @param systemCode
     *            システムコード(例えばSuicaは0x0003、ワイルドカードは0xFFFF)
     * @throws FelicaPollingException
     *             ポーリングエラー
     */
    public void polling(final int systemCode) throws FelicaPollingException {
        FelicaLib.INSTANCE.felica_free(this.felicaHandle);
        this.felicaHandle =
            FelicaLib.INSTANCE.felica_polling(this.pasoriHandle, systemCode,
                    (byte) 0, (byte) 0);
        if (this.felicaHandle == Pointer.NULL) {
            throw new FelicaPollingException("no polling executed.");
        }
    }

    /**
     * @param systemCode
     *            システムコード(例えばSuicaは0x0003、ワイルドカードは0xFFFF)
     * @param serviceCode
     *            サービスコード
     * @param addr
     *            アドレス
     * @return byte[16]
     * @throws FelicaPollingException
     *             ポーリングエラー
     */
    @SuppressWarnings("boxing")
    public String read(
            final int systemCode,
            final int serviceCode,
            final byte addr) throws FelicaPollingException {
        final byte[] buf = new byte[16];
        if (this.felicaHandle == Pointer.NULL) {
            this.polling(systemCode);
        }
        FelicaLib.INSTANCE.felica_read_without_encryption02(this.felicaHandle,
                serviceCode, 0, addr, buf);
        return String
                .format(
                        Felica.BYTE16_FORMAT,
                        buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6],
                        buf[7], buf[8], buf[9], buf[10], buf[11], buf[12],
                        buf[13], buf[14], buf[15]);
    }

    /**
     * @param systemCode
     *            システムコード(例えばSuicaは0x0003、ワイルドカードは0xFFFF)
     * @param serviceCode
     *            サービスコード
     * @param addr
     *            アドレス
     * @param buf
     *            書込みbyte[16]
     * @return errorcode
     * @throws FelicaPollingException
     *             ポーリングエラー
     */
    public int write(
            final int systemCode,
            final int serviceCode,
            final byte addr,
            final byte[] buf) throws FelicaPollingException {
        if (this.felicaHandle == Pointer.NULL) {
            this.polling(systemCode);
        }
        return FelicaLib.INSTANCE.felica_write_without_encryption(
                this.felicaHandle, serviceCode, addr, buf);
    }
}
